package org.jetlinks.pro.things.impl.connector.script;

import groovy.lang.MissingPropertyException;
import groovy.lang.Script;
import io.netty.util.internal.ObjectPool;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.core.device.DeviceThingType;
import org.jetlinks.core.message.ThingMessage;
import org.jetlinks.core.message.property.PropertyMessage;
import org.jetlinks.core.things.ThingProperty;
import org.jetlinks.core.things.ThingType;
import org.jetlinks.core.things.ThingsDataManager;
import org.jetlinks.reactor.ql.utils.CastUtils;
import org.jetlinks.reactor.ql.utils.CompareUtils;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.ThreadSafe;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.DoubleAdder;
import java.util.function.Function;
import java.util.function.Supplier;

@Slf4j
@NotThreadSafe
public abstract class ThingMessageScript extends Script implements ThingDataHelper {

    private ObjectPool.Handle<ThingMessageScript> handle;

    public static ThingsDataManager RECENT_DATA_MANAGER;

    private final ThingsDataManager dataManager = RECENT_DATA_MANAGER;

    @Setter
    private Scheduler scheduler = Schedulers.boundedElastic();

    @Setter
    private ThingType defaultThingType;

    @Setter
    @Getter
    private ThingMessage message;

    @Override
    public Logger getLogger() {
        return log;
    }

    public String getThingId() {
        return (String) getProperty("thingId");
    }

    public long getTimestamp() {
        return (Long) getProperty("timestamp");
    }

    public long getTimestamp(String property) {

        ThingMessage message = getMessage();
        if (null == message) {
            return getTimestamp();
        }
        if (message instanceof PropertyMessage) {
            return ((PropertyMessage) message)
                .getPropertySourceTime(property)
                .orElse(message.getTimestamp());
        }
        return message.getTimestamp();
    }

    public ThingType getThingType() {
        ThingMessage message = getMessage();
        if (message != null) {
            return ThingType.of(message.getThingType());
        }
        return defaultThingType;
    }

    /*======== 内置函数 ======== */
    public long $now() {
        return System.currentTimeMillis();
    }

    public String $now(String format) {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(format));
    }

    public Double $sum(Object... params) {
        DoubleAdder adder = new DoubleAdder();
        for (Object d : params) {
            adder.add(CastUtils.castNumber(d).doubleValue());
        }
        return adder.sum();
    }

    public Object $max(Object... params) {
        Object max = null;
        for (Object param : params) {
            if (max == null) {
                max = param;
                continue;
            }
            if (CompareUtils.compare(max, param) < 0) {
                max = param;
            }
        }
        return max;
    }

    public Object $min(Object... params) {
        Object min = null;
        for (Object param : params) {
            if (min == null) {
                min = param;
                continue;
            }
            if (CompareUtils.compare(min, param) > 0) {
                min = param;
            }
        }
        return min;
    }

    @Override
    public Object getProperty(String property) {
        return super.getProperty(property);
    }

    public ThingsDataManager getDataManager() {
        try {
            Object manager = getBinding().getVariable(ThingsDataManager.class.getName());
            if (manager instanceof ThingsDataManager) {
                return ((ThingsDataManager) manager);
            }
        } catch (MissingPropertyException ignore) {

        }
        return dataManager;
    }

    @Override
    public Object invokeMethod(String name, Object args) {
        // FIXME: 2021/11/30 动态获取其他物类型?
        if ("$device".equals(name)) {
            Object[] argsArr = ((Object[]) args);
            if (argsArr.length != 1) {
                throw new IllegalArgumentException("$device method need 1 args");
            }
            return new DefaultThingDataHelper(DeviceThingType.device, String.valueOf(argsArr[0]),getDataManager());
        }
        return super.invokeMethod(name, args);
    }

    @Override
    public void print(Object value) {
        log.debug(String.valueOf(value));
    }

    @Override
    public void printf(String format, Object value) {
        if (log.isDebugEnabled()) {
            log.debug(String.format(format, value));
        }
    }

    @Override
    public void printf(String format, Object[] values) {
        if (log.isDebugEnabled()) {
            log.debug(String.format(format, values));
        }
    }

    @Override
    public void println() {

    }

    @Override
    public void println(Object value) {
        log.debug(String.valueOf(value));
    }

    public Logger logger() {
        return log;
    }

    /*========对象池相关======== */

    //释放当前对象
    void release() {
        if (handle != null) {
            handle.recycle(this);
        }
    }

    void setHandle(ObjectPool.Handle<ThingMessageScript> handle) {
        if (this.handle != null) {
            throw new IllegalStateException();
        }
        this.handle = handle;
    }

    private Mono<Object> doRunAsync() {
        Object value = run();
        if (value instanceof Publisher) {
            return Mono.from((Publisher<?>) value);
        }
        if (value == null) {
            return Mono.empty();
        }
        return Mono.just(value);
    }

    Mono<Object> runAsync() {
        return Mono
            .defer(this::doRunAsync)
            .subscribeOn(scheduler);
    }
}
